function CanvasAsset(callback)
{
  this.loaded = false;
  this.callback = callback;
  this.object = null;
  
  this.isloadedCallback = function (e)
  {
    this.loaded = true;
    this.callback();
  }

  this.isLoaded = function ()
  {
    return this.loaded;
  }
}

function CanvasImage(src,callback)
{
  this.inheritFrom = CanvasAsset;
  this.inheritFrom(callback);

  this.object = new Image();
  this.object.addEventListener('load',bind(this,this.isloadedCallback),true);
  this.object.src = src;
}

function CanvasAudio(src,callback)
{
  this.inheritFrom = CanvasAsset;
  this.inheritFrom(callback);

  this.object = new Audio(src);
  this.object.autoplay = true;
  //this.object.addEventListener('load',bind(this,this.isloadedCallback),true);
  this.object.load();
  this.object.play();
  //this.loaded = true; 
}

function CanvasLayer(name)
{
  this.element = document.getElementById(name);
  
  if(this.element.getContext)
  {
    this.context = this.element.getContext('2d');
  } else {
    throw new Error('Canvas ('+name+'): Cannot get Context');
  }
  
  this.drawLine = function (s,x1,y1,x2,y2)
  {
    s.setup(this.context);
        
    this.context.beginPath();
    
    this.context.moveTo(x1,y1);
    this.context.lineTo(x2,y2);

    s.use(this.context);
        
    this.context.closePath();    
  }
  
  this.drawRect = function (s,x,y,w,h)
  {
    s.setup(this.context);
        
    this.context.beginPath();
    
    this.context.moveTo(x,y);
    this.context.lineTo(x+w,y);
    this.context.lineTo(x+w,y+h);
    this.context.lineTo(x,y+h);
    this.context.lineTo(x,y);
    
    s.use(this.context);
        
    this.context.closePath();
  }
  
  this.getImageData = function (x,y,w,h)
  {
    return this.context.getImageData(x,y,w,h)
  }
  
  this.putImageData = function (x,y,data)
  {
    this.context.putImageData(data,x,y);
  }
  
  this.drawText = function (s,x,y,text)
  {
    s.setup(this.context);
    
    if(s.fillColour)
      this.context.fillText(text,x,y);
    else
      this.context.strokeText(text,x,y);
  }
  
  this.drawImage = function (img,x,y,w,h)
  {
    this.context.drawImage(img,x,y,w,h);
  }
  
  this.clearRect = function (x,y,w,h)
  {
    this.context.clearRect(x,y,w,h);
  }
  
  this.save = function ()
  {
    this.context.save();
  }
  
  this.restore = function ()
  {
    this.context.restore();
  }
  
  this.translate = function (x,y)
  {
    this.context.translate(x,y);
  }
  
  this.rotate = function (a)
  {
    this.context.rotate(a);
  }
}

function rgb(r,g,b,a)
{
  if(!a)
  {
    return 'rgb(' + r + ',' + g + ',' + b + ')';
  } else {
    return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
  }  
}

function CanvasStyle(fillColour, strokeColour, strokeWidth)
{
  if(fillColour)
    this.fillColour = fillColour;
  
  if(strokeColour)
    this.strokeColour = strokeColour;

  if(strokeWidth)    
    this.strokeWidth = strokeWidth;

  this.strokeAdvanced = function (strokeCap, strokeJoin, strokeMiter)
  {
    if(strokeCap)
      this.strokeCap = strokeCap;
      
    if(strokeJoin)
      this.strokeJoin = strokeJoin;
      
    if(strokeMiter)
      this.strokeMiter = strokeMiter;    
  }
  
  this.fontAdvanced = function (font, align, baseline)
  {
    if(font)
      this.font = font;
    
    if(align)
      this.align = align;
      
    if(baseline)
      this.baseline = baseline;
  }
  
  this.setup = function(context)
  {
    if(this.fillColour)
      context.fillStyle = this.fillColour;
    
    if(this.strokeColour)
      context.strokeStyle = this.strokeColour;
      
    if(this.strokeWidth)  
      context.lineWidth = this.strokeWidth;
      
    if(this.strokeCap)
      context.lineCap = this.strokeCap;
    
    if(this.strokeJoin)
      context.lineJoin = this.strokeJoin;
    
    if(this.strokeMiter)
      context.miterLimit = this.strokeMiter;
      
    if(this.font)
      context.font = this.font;
      
    if(this.align)
      context.textAlign = this.align;
      
    if(this.baseline)
      context.textBaseline = this.baseline;
  }
  
  this.use = function(context)
  {
    if(this.fillColour)
      context.fill();
    
    if(this.strokeColour && this.strokeWidth)
      context.stroke();
  }  
}


function BaseStyles()
{
  this.styles = Array();
   
  this.addStyle = function (name,style) 
  {
      this.styles[name] = style;
      return this.getStyle(name);
  }
  
  this.getStyle = function (name)
  {
    return this.styles[name];
  }  
}

function BaseLayers()
{
  this.layers = Array();
  
  this.addDrawLayer = function (name)
  {
    this.layers.push(new CanvasLayer(name));
  }
  
  this.getDrawLayer = function (pos)
  {
    if(isNaN(pos))
      return pos;
      
    return this.layers[pos];
  }  
  
  this.isTopDrawLayer = function (l)
  {
    return l == this.layers.length - 1;
  }
  
  this.getTopDrawLayer = function ()
  {
    return this.layers[this.layers.length - 1];
  }
}

function Gizmo(name,colour,mouseClickCallback,mouseOverCallback)
{
  this.name = name;
  this.mouseClickCallback = mouseClickCallback;
  this.mouseOverCallback = mouseOverCallback;
  this.colour = colour;
  this.style = new CanvasStyle(rgb(colour,colour,colour));
}


function BaseInteraction()
{ 
  this.inheritFrom = BaseLayers;
  this.inheritFrom();  
  
  this.clickLayer = null;
  this.offsetTop = 0;
  this.offsetLeft = 0;
  this.mouseMoveCallback = null;
  

  this.calculateOffset = function (object)
  {   
    this.offsetTop += object.offsetTop;
    this.offsetLeft += object.offsetLeft;
   
    if(object.offsetParent) 
      this.calculateOffset(object.offsetParent);
  }
    
  this.addClickLayer = function (name)
  {
    this.clickLayer = new CanvasLayer(name);
    
    this.getTopDrawLayer().element.addEventListener('mousemove',bind(this,this.clickLayerMouseMove),true);
    this.getTopDrawLayer().element.addEventListener('click',bind(this,this.clickLayerClick),true);
    this.calculateOffset(this.getTopDrawLayer().element);
  }
  
  //this.canvasStyle = new CanvasStyle(rgb(255,0,0));
  //this.canvasStyle.fontAdvanced('20pt Arial');
  
  this.findMatchingGizmo = function (x,y)
  {
    var pixel = this.clickLayer.context.getImageData(x,y,1,1).data;
    if(pixel[3] != 0)
    {
      var matchColour = rgb(pixel[0],pixel[1],pixel[2])
      //this.clickLayer.drawText(this.canvasStyle,x,y,pixel);
      for(var i=0;i<this.gizmos.length;i++)
        if(this.gizmos[i].style.fillColour == matchColour)     
          return i;
    }
    
    return null;    
  }
  
  this.clickLayerMouseMove = function (e)
  {
    var posx = e.pageX - this.offsetLeft;
    var posy = e.pageY - this.offsetTop;

    var gizmo = this.findMatchingGizmo(posx,posy);
    if(gizmo != null && this.gizmos[gizmo].mouseOverCallback)
      this.gizmos[gizmo].mouseOverCallback(posx,posy,this.gizmos[gizmo].name);
    else if(this.mouseMoveCallback)
      this.mouseMoveCallback(posx,posy);
  }
  
  this.clickLayerClick = function (e)
  {
    var posx = e.pageX - this.offsetLeft;
    var posy = e.pageY - this.offsetTop;

    var gizmo = this.findMatchingGizmo(posx,posy);
    if(gizmo != null && this.gizmos[gizmo].mouseClickCallback)
      this.gizmos[gizmo].mouseClickCallback(posx,posy,this.gizmos[gizmo].name);
  }
    
  
  this.hasClickLayer = function ()
  {
    return this.clickLayer != null;
  }  
  
  this.generateGizmoColour = function ()
  {
    var maxColour = 255 
    var v = Math.floor(maxColour - (maxColour / Math.pow(2,this.gizmos.length)));
    return v;
  }
  
  this.startGizmo = function (name,mouseClickCallback,mouseOverCallback)
  {
    var gizmo = new Gizmo(name,this.generateGizmoColour(),mouseClickCallback,mouseOverCallback);
    this.currentGizmo = gizmo;
    this.gizmos.push(gizmo);    
  }
  
  this.stopGizmo = function ()
  {
    this.currentGizmo = null;
  }
  
  this.clearGizmos = function ()
  {
    this.stopGizmo();
    this.gizmos = Array();    
  }
  
  this.hasGizmo = function ()
  {
    return this.currentGizmo != null;
  }
  
  this.clearGizmos();
}

function Engine()
{
  this.inheritFrom = BaseStyles;
  this.inheritFrom();
  
  this.inheritFrom = BaseInteraction;
  this.inheritFrom();
  
  this.drawLine = function (l,s,x1,y1,x2,y2)
  {
    this.getDrawLayer(l).drawLine(s,x1,y1,x2,y2);
    
    if(this.isTopDrawLayer(l) && this.hasClickLayer() && this.hasGizmo())
      this.clickLayer.drawLine(this.currentGizmo.style,x1,y1,x2,y2);    
  }
  
  this.drawText = function (l,s,x,y,text)
  {
    this.getDrawLayer(l).drawText(s,x,y,text);
    
    if(this.isTopDrawLayer(l) && this.hasClickLayer() && this.hasGizmo())
      this.clickLayer.drawText(this.currentGizmo.style,x,y,text); 
  }
  
  this.drawRect = function (l,s,x,y,w,h)
  {
    this.getDrawLayer(l).drawRect(s,x,y,w,h);
    
   if(this.isTopDrawLayer(l) && this.hasClickLayer() && this.hasGizmo())
      this.clickLayer.drawRect(this.currentGizmo.style,x,y,w,h);
  }
  
  this.drawImage = function (l,img,x,y,w,h)
  {
    this.getDrawLayer(l).drawImage(img,x,y,w,h);
    
    if(this.isTopDrawLayer(l) && this.hasClickLayer() && this.hasGizmo())
    {
      var compositionTemp = this.clickLayer.context.globalCompositeOperation; 
      
      this.clickLayer.drawImage(img,x,y,w,h);
      var pixels = this.clickLayer.getImageData(x,y,w,h);
      for(var i=0;i<pixels.data.length;i+=4)
      {
        if(pixels.data[i+3] != 0)
        {
          pixels.data[i] = pixels.data[i + 1] = pixels.data[i + 2] = this.currentGizmo.colour;
          pixels.data[i + 3] = 255;
        }  
      }
      
      this.clickLayer.clearRect(x,y,w,h);
      this.clickLayer.putImageData(x,y,pixels);
      
      //this.clickLayer.context.globalCompositeOperation = 'source-atop';
      //this.clickLayer.drawRect(this.currentGizmo.style,x,y,w,h);
      //this.clickLayer.context.globalCompositeOperation = compositionTemp;
    }
  }
  
  this.clearRect = function (l,x,y,w,h)
  {
    if(this.isTopDrawLayer(l) && this.hasClickLayer())
      this.clickLayer.clearRect(x,y,w,h);
    this.getDrawLayer(l).clearRect(x,y,w,h);
  }  
  
  this.save = function (l)
  {
    if(this.isTopDrawLayer(l) && this.hasClickLayer())
      this.clickLayer.save();
    this.getDrawLayer(l).save();
  }
  
  this.restore = function (l)
  {
    if(this.isTopDrawLayer(l) && this.hasClickLayer())
      this.clickLayer.restore();
    this.getDrawLayer(l).restore();
  }
  
  this.translate = function (l,x,y)
  {
    if(this.isTopDrawLayer(l) && this.hasClickLayer())
      this.clickLayer.translate(x,y);
    this.getDrawLayer(l).translate(x,y);    
  }
  
  this.rotate = function (l,a)
  {
    if(this.isTopDrawLayer(l) && this.hasClickLayer())
      this.clickLayer.rotate(a);
    this.getDrawLayer(l).rotate(a);      
  }  
}
